#ifndef PUDGE_ENGINE_MAIN_HPP
#define PUDGE_ENGINE_MAIN_HPP

// next three includes are for memory debugging

//#define _CRTDBG_MAP_ALLOC
//#include <stdlib.h>
//#include <crtdbg.h>

#include <stdio.h>
#include <fcntl.h>
#include <io.h>

#include <cstdlib>
#include <iostream>
#include <fstream>

#include <vector>

#define WINDOW_NAME "PUDGE"

//	http://www.halcyon.com/~ast/dload/guicon.htm

void io_to_console() 
{
	static const WORD MAX_CONSOLE_LINES = 500;

	int hConHandle;
	long lStdHandle;

	CONSOLE_SCREEN_BUFFER_INFO coninfo;

	FILE *fp;

	// allocate a console for this app

	AllocConsole();

	// set the screen buffer to be big enough to let us scroll text

	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
	coninfo.dwSize.Y = MAX_CONSOLE_LINES;
	SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);

	// redirect unbuffered STDOUT to the console

	lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE); 
	hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
	fp = _fdopen( hConHandle, "w" );
	*stdout = *fp;
	setvbuf( stdout, NULL, _IONBF, 0 );

	// redirect unbuffered STDIN to the console

	lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);
	hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
	fp = _fdopen( hConHandle, "r" );
	*stdin = *fp;
	setvbuf( stdin, NULL, _IONBF, 0 );

	// redirect unbuffered STDERR to the console

	lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
	hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);

	fp = _fdopen( hConHandle, "w" );
	*stderr = *fp;
	setvbuf( stderr, NULL, _IONBF, 0 );

	// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
	// point to console as well

	std::ios::sync_with_stdio();
}

/// Return static game_context object

game_context & get_game_context()
{
	static game_context * gcon = NULL;
	
	if(gcon == NULL) gcon = new game_context();

	return *gcon;
}

LONG WINAPI wndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain
	(	HINSTANCE hInstance
	,   HINSTANCE hPvarInstance
	,   LPSTR lpCmdLine
	,   int nCmdShow )
{
	io_to_console();

	HWND hwnd;
	HDC hdc;
	HGLRC hrc;
	WNDCLASS wc;
	MSG msg;
	BOOL bret;
	PIXELFORMATDESCRIPTOR pfd;
	int format;
	bool alive = true;

	// Setup class 

	wc.style = 0;
	wc.lpfnWndProc = wndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = 0;
	wc.hCursor = LoadCursor (NULL,IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wc.lpszMenuName = WINDOW_NAME;
	wc.lpszClassName = WINDOW_NAME;

	// Register class

	if(!RegisterClass(&wc)) return FALSE;

	// Create the window

	hwnd = CreateWindow( 
		WINDOW_NAME, WINDOW_NAME,
		WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		(HWND)NULL,
		(HMENU)NULL,
		hInstance,
		(LPVOID)NULL );

	if(!hwnd)return false;

	// Setup pixel format descriptor

	ZeroMemory(&pfd, sizeof(PIXELFORMATDESCRIPTOR));
	pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion = 1;
	pfd.dwFlags = 
		PFD_DRAW_TO_WINDOW |
		PFD_SUPPORT_OPENGL |
		PFD_DOUBLEBUFFER;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 24;
	pfd.cDepthBits = 8;
	pfd.iLayerType = PFD_MAIN_PLANE;

	// Find and set pixel format

	hdc = GetDC(hwnd);
	format = ChoosePixelFormat(hdc, &pfd);
	SetPixelFormat(hdc, format, &pfd); 

	// Setup opengl context

	hrc = wglCreateContext(hdc);
	wglMakeCurrent(hdc,hrc);

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);

	glewInit();

	game_context & gcon = get_game_context();
	
	GLint vp[4];
	glGetIntegerv(GL_VIEWPORT, vp);
	gcon.viewport(game_context::viewport_type(vp[0], vp[1], vp[2], vp[3]));

	init_game_engine(gcon);

	alive = true;
	while(alive)
	{
		if( (bret = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) != 0 )
		{
			if(bret == -1)
			{
				alive = false;
				break;
			}
			if(msg.message == WM_QUIT)
			{
				alive = false;
				break;
			}

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		tick_game_engine(gcon);
		gcon.reset();

		SwapBuffers(hdc);
	}

	glFinish();

	kill_game_engine(gcon);
	delete &gcon;

	// Destroy opengl
	wglMakeCurrent(NULL,NULL);
	wglDeleteContext(hrc);
	ReleaseDC(hwnd,hdc);

	return 0;
}

/// window procedure

LONG WINAPI wndProc
	(	HWND hwnd
	,	UINT msg
	,	WPARAM wparam
	,	LPARAM lparam )
{
	typedef game_context::mouse_event_type MouseEvent;
	typedef game_context::key_event_type KeyEvent;

	if(msg == WM_KEYDOWN)
	{
		/*	artifice::key_event e;
		e.c = (char)MapVirtualKey(wparam,2);
		get_game().handle(e);*/
		return 0;
	}
	if(msg == WM_MOUSEMOVE)
	{
		const int height = get_game_context().viewport().h;
		MouseEvent m;
		m.x = GET_X_LPARAM(lparam);
		m.y = height - GET_Y_LPARAM(lparam);
		m.hover = true;
		get_game_context().mouse_events().push_back(m);
		return 0;
	}
	if(msg == WM_LBUTTONDOWN)
	{
		const int height = get_game_context().viewport().h;
		MouseEvent m;
		m.x = GET_X_LPARAM(lparam);
		m.y = height - GET_Y_LPARAM(lparam);
		m.left_down = true;
		get_game_context().mouse_events().push_back(m);
		return 0;
	}
	if(msg == WM_LBUTTONUP)
	{
		const int height = get_game_context().viewport().h;
		MouseEvent m;
		m.x = GET_X_LPARAM(lparam);
		m.y = height - GET_Y_LPARAM(lparam);
		m.left_up = true;
		get_game_context().mouse_events().push_back(m);
		return 0;
	}
	if(msg == WM_SIZE)
	{
		game_context::viewport_type vp
			(	0
			,	0
			,	GET_X_LPARAM(lparam)
			,	GET_Y_LPARAM(lparam) );

		glViewport(0,0,vp.w,vp.h);
		get_game_context().viewport(vp);

		return 0;
	}
	if(msg == WM_CREATE)
	{
		return 0;
	}
	if(msg == WM_CLOSE)
	{
		PostQuitMessage(0);
		return 0;
	}
	if(msg == WM_DESTROY)
	{
		PostQuitMessage(0);
		return 0;
	}
	if(msg == WM_CHAR)
	{
		return 0;
	}

	return DefWindowProc(hwnd,msg,wparam,lparam);
}

#endif